package com.example.edu.web.handler;

import com.example.edu.config.LocaleService;
import com.example.edu.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.NoSuchMessageException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

/**
 * @author yanzhao
 * @date 2021/12/21 16:45
 * @since 1.0.0
 */
@RestControllerAdvice
public class ExceptionAdvice {

    public static final Logger LOGGER = LoggerFactory.getLogger(ExceptionAdvice.class);

    @Resource
    private LocaleService localeService;

    @ExceptionHandler(NoSuchMessageException.class)
    public Object noSuchMessageException(NoSuchMessageException e) {
        LOGGER.info("i18n消息缺失：{}", e.getMessage());
        return Result.error(localeService.getMessage("system.i18n.message.missing"), Result.StatusCode.NO_SUCH_MESSAGE_KEY);
    }

    @ExceptionHandler(DuplicateKeyException.class)
    public Object duplicateKeyException(DuplicateKeyException e) {
        LOGGER.info("数据重复提交：{}", e.getMessage());
        return Result.error(localeService.getMessage("data.already.duplicate"), Result.StatusCode.DUPLICATE_KEY);
    }

    /**
     * 参数前加上@Valid或Spring的@Validated注解，触发校验标准bean验证，
     * 如果校验不通过，抛出BindException并且响应码编程400(BAD_REQUEST)，
     * 或者使用Errors或BindingResult参数在控制器内自己处理验证错误
     * 如果方法参数定义了BindingResult参数，意味着用户要自己处理校验数据，Spring不再拦截校验异常
     *
     * @param e 绑定验证异常
     * @return 错误返回消息
     */
    @ExceptionHandler(BindException.class)
    public Object validateErrorHandler(BindException e) {
        ObjectError error = e.getAllErrors().get(0);
        LOGGER.info("数据验证异常：{}", error.getDefaultMessage());
        return Result.error(error.getDefaultMessage(), Result.StatusCode.PARAM_MISSING);
    }

    /**
     * 参数前加上@Valid或Spring的@Validated注解，触发校验标准bean验证，
     * 如果校验不通过，抛出BindException并且响应码编程400(BAD_REQUEST)，
     * 或者使用Errors或BindingResult参数在控制器内自己处理验证错误；
     * 如果方法参数定义了BindingResult参数，意味着用户要自己处理校验数据，Spring不再拦截校验异常；
     * 如果参数前有@RequestBody，验证错误会抛出MethodArgumentNotValidException
     *
     * @param e 绑定验证异常
     * @return 错误返回消息
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object validateErrorHandler(MethodArgumentNotValidException e) {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        LOGGER.info("数据验证异常：{}", error.getDefaultMessage());
        return Result.error(error.getDefaultMessage(), Result.StatusCode.PARAM_MISSING);
    }

    /**
     * 类上必须使用@Validated，必须配置MethodValidationPostProcessor
     * 方法参数或返回值上的约束注解校验才会生效
     * 校验不通过，会抛出ConstraintViolationException
     *
     * @param e 绑定验证异常
     * @return 错误返回消息
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Object defaultErrorHandler(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        LOGGER.info("数据验证异常：{}", violation.getMessage());
        return Result.error(violation.getMessage(), Result.StatusCode.PARAM_MISSING);
    }

}
